﻿// 
//    Copyright 2010 The JaernCloud Project
// 
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
// 
//        http://www.apache.org/licenses/LICENSE-2.0
// 
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
// 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JaernCloud.Language.Compiler.Ast;

namespace JaernCloud.Language.Compiler.Types {
        
    /// <summary>
    /// Implements an identifier scope for resolving node types using node identifier tag selectors
    /// </summary>
    public class NodeIdentifierScope : INodeIdentifierScope {

        public INodeType Context { get; private set; }

        public NodeIdentifierScope(INodeType context) {
            this.Context = context;
        }

        public List<INodeType> Lookup(AstTagSelector selector) {

            if (selector.Tags.Count > 1) {
                throw new NotImplementedException("Tag selectors with multiple tags are not yet supported");
            }

            foreach (AstTag tag in selector.Tags) {

                INodeType builtInNodeType = NodeTypeGenerator.GetBuiltInNodeType(tag);
                if(builtInNodeType != null) {
                    return builtInNodeType.AsList();
                }

                if (Context.Parent != null) {
                    if(Context.Parent.MatchesTag(tag)) {
                        return Context.Parent.AsList();
                    }
                    foreach (INodeType nodeType in Context.Parent.Children) {
                        if (nodeType == Context) {
                            continue;
                        }
                        if (nodeType.MatchesTag(tag)) {
                            return nodeType.AsList();
                        }
                    }

                    // TODO: Check further up the tree.

                }

            }

            // try the root scope
            var root = Context;
            while (root.Parent != null) {
                root = root.Parent;
            }
            var currentTag = selector.Tags[0];
            foreach (INodeType globalType in root.Children) {
                if (globalType.MatchesTag(currentTag)) {
                    return globalType.AsList();
                }
                // TODO: Remove this when scope traverses up the tree properly, but currently needed for actions
                foreach (var globalTypeChild in globalType.Children) {
                    if (globalTypeChild.MatchesTag(currentTag)) {
                        return globalTypeChild.AsList();
                    }
                }
            }

            // TODO?
            return null;
        }

        public INodeType Lookup(AstExpression expression) {

            AstPropertyExpression propertyExpression = expression as AstPropertyExpression;
            INodeType owner = null;
            INodeType result = null;
            // TODO: Decide on the rules behind expression that match (node).property1.property2 and (node.classnameisproperty1).property2!
            foreach (String propertyName in propertyExpression.PropertyNames) {
                if (owner == null) {
                    // find the owner node type for the expression
                    if (propertyName.StartsWith("@")) {
                        // contextual identifier
                        switch (propertyName) {
                            case "@user":
                                return NodeTypeGenerator.GetBuiltInNodeType(AstTag.FromName("user"));
                            default:
                                throw new NotImplementedException(propertyName + " is not yet implmented");
                        }
                    }
                    var parent = Context.Parent;
                    while (parent != null && owner == null) {
                        foreach (var child in parent.Children) {
                            if (child.Names.Contains(propertyName)) {
                                owner = child;
                                break;
                            }
                        }
                        if (owner == null) {
                            parent = parent.Parent;
                        }
                    }
                    if (owner == null) {
                        throw new UnknownIdentifierException(propertyName, Context.SourceNode);
                    }
                    result = owner;
                } else {
                    // get members
                    foreach (var child in result.Children) {
                        if (child.Names.Contains(propertyName)) {
                            result = child;
                            break;
                        }
                    }
                }
            }

            return result;


        }

        public INodeType Lookup(AstParameter parameter) {
            throw new NotImplementedException("Parameters are not yet supported");
        }

    }

    public static class NodeIdentifierScopeExtensions {

        public static List<INodeType> AsList(this INodeType nodeType) {
            var ret = new List<INodeType>(1);
            ret.Add(nodeType);
            return ret;
        }

        public static bool MatchesTag(this INodeType nodeType, AstTag tag) {
            
            if(nodeType.SourceNode.Tag != null) {
                if(nodeType.SourceNode.Tag.Matches(tag)) {
                    return true;
                }
            }
            if (nodeType.SourceNode.Query != null) {
                var identifier = nodeType.SourceNode.Query.Identifier;
                if (identifier != null) {
                    if (AstTag.FromName(identifier).Matches(tag)) {
                        return true;
                    }
                }
            }
            foreach (INodeType inheritsFrom in nodeType.InheritsFrom) {
                if (inheritsFrom.MatchesTag(tag)) {
                    return true;
                }
            }

            return false;
        }

    }

}
